home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1995 February: Tool Chest / Dev.CD Feb 95 / Dev.CD Feb 95.toast / Tool Chest / QuickDraw GX / QuickDraw GX Info / QuickDraw GX Interfaces / Interfaces & Libraries / graphics libraries / shape controls library.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-04-30  |  44.2 KB  |  1,335 lines  |  [TEXT/MPS ]

  1. /* gxShape controls library:
  2.     library to allow the user to manipulate (gxTransform) a set of shapes
  3.     by Dave Good and Jeff Kreegar
  4.     Copyright 1991 - 1992 Apple Computer, Inc.    All rights reserved.    */
  5.  
  6. #include <Memory.h>
  7. #include <Events.h>
  8. #include <OSUtils.h>
  9. #include <ToolUtils.h>
  10.  
  11. #include "graphics debugging.h"
  12. #include "graphics errors.h"
  13. #include "graphics toolbox.h"
  14. #include "graphics libraries.h"
  15. #include "shape controls library.h"
  16.  
  17.  
  18. /*----------------------------------------------------------------------------------------------------*/
  19. /* private constants and structures */
  20. /*----------------------------------------------------------------------------------------------------*/
  21.  
  22.  
  23. /*** this is not used or completed; however it is a placeholder for the future */
  24. typedef struct portBuffer {
  25.     gxViewGroup   group;
  26. } portBuffer;
  27.  
  28.  
  29. typedef struct shapeControlRecord {
  30.     /* ----- can be accessed in a public way ----- */
  31.     gxShape       items;
  32.     gxShape       foreground;
  33.     gxShape       background;
  34.     gxShape       selection;
  35.     /* ----- private fields follow ----- */
  36.     gxViewPort          **originalPorts;
  37.     gxShape       controls;
  38.     gxPoint           savedCenter;              /* this is either (gxPositiveInfinity, gxPositiveInfinity) or the saved rotate center */
  39.     long            buildHiddenLevel;
  40.     gxRectangle       **invalidAreas;
  41. } shapeControlRecord, *shapeControlPtr;
  42.  
  43.  
  44. #define controlHandleGap        ff(6)        /* this is the distance between the bounds of the gxShape and the bounds of the control handles */
  45. #define minimumObjectSizeH        ff(2)        /* this is the smallest horizontal size that an object may be scaled to */
  46. #define minimumObjectSizeV        ff(2)        /* this is the smallest vertical size that an object may be scaled to */
  47. #define rotateControlRadius     ff(7)
  48. #define rotateCenterSize        ff(2)
  49.  
  50.  
  51. typedef enum {
  52.     /* ----- special commands ----- */
  53.     firstSpecialCommand = -1,
  54.     commandSelect = firstSpecialCommand,
  55.     commandMove,
  56.     lastSpecialCommand = commandMove,
  57.     /* ----- indexes into the shapeControl->controls picture that correspond to different commands ----- */
  58.     firstIndexCommand = 1,
  59.     commandRotate = firstIndexCommand,
  60.     commandMoveRotateCenter,
  61.     commandScale,
  62.     commandSkew = commandScale + 4,
  63.     lastIndexCommand = commandSkew + 3,
  64.     /* ----- modifiers for commandScale follow ----- */
  65.     leftTop = 0,
  66.     rightTop,
  67.     rightBottom,
  68.     leftBottom,
  69.     /* ----- modifiers for commandSkew follow ----- */
  70.     centerTop = 0,
  71.     centerRight,
  72.     centerBottom,
  73.     centerLeft
  74. } shapeControlCommands;
  75.  
  76.  
  77. /*----------------------------------------------------------------------------------------------------*/
  78. /* private generic routines */
  79. /*----------------------------------------------------------------------------------------------------*/
  80.  
  81.  
  82. static Fixed FixedAbs(Fixed value)
  83. {
  84.     if( value < 0 )
  85.         return -value;
  86.     return value;
  87. }
  88.  
  89. static gxViewPort *DereferenceViewPortList(gxViewPort **list)
  90. {
  91.     HLock((Handle)list);
  92.     return *list;
  93. }
  94.  
  95. static void ReleaseViewPortList(gxViewPort **list)
  96. {
  97.     HUnlock((Handle)list);
  98. }
  99.  
  100. static gxViewPort **NewViewPortListFromShape(gxShape source)
  101. {
  102.     gxViewPort **list = (gxViewPort **)NewHandleClear((GXGetShapeViewPorts(source, nil) + 1) * sizeof(gxViewPort));
  103.  
  104.     IfErrorReturnNil(list == nil, out_of_memory);
  105.     GXGetShapeViewPorts(source, DereferenceViewPortList(list));
  106.     ReleaseViewPortList(list);
  107.     return list;
  108. }
  109.  
  110. static long CountViewPortList(gxViewPort **list)
  111. {
  112.     return (GetHandleSize((Handle)list) / sizeof(gxViewPort)) - 1;
  113. }
  114.  
  115. static void DisposeViewPortList(gxViewPort **list)
  116. {
  117.     NilParamReturn(list);
  118.     DisposeHandle((Handle)list);
  119. }
  120.  
  121. static boolean ShapeInPicture(gxShape test, gxShape picture)
  122. {
  123.     long count;
  124.  
  125.     NilShapeReturnNil(test);
  126.     NilShapeReturnNil(picture);
  127.  
  128.     count = GXGetPicture(picture, nil, nil, nil, nil);
  129.     while( count ) {
  130.         gxShape item;
  131.  
  132.         GXGetPictureParts(picture, count, 1, &item, nil, nil, nil);
  133.         if( item == test )
  134.             return true;
  135.         --count;
  136.     }
  137.     return false;
  138. }
  139.  
  140. static void GetPictureBounds(gxShape picture, gxRectangle *bounds, gxMapping *concatMatrix)
  141. {
  142.     long count = GXGetPicture(picture, nil, nil, nil, nil);
  143.  
  144.     /* if there are no items in the picture, then there is nothing to do */
  145.     if( count == 0 )
  146.         return;
  147.  
  148.     do {    gxShape item;
  149.         gxTransform itemXform;
  150.         gxMapping matrix;
  151.  
  152.         GXGetPictureParts(picture, count, 1, &item, nil, nil, &itemXform);
  153.  
  154.         /* if there was no overriding gxTransform, then use the item’s gxTransform. calculate the concatenated gxMapping */
  155.         if( itemXform == nil )
  156.             itemXform = GXGetShapeTransform(item);
  157.         MapMapping(GXGetTransformMapping(itemXform, &matrix), concatMatrix);
  158.  
  159.         if( GXGetShapeType(item) == gxPictureType ) {
  160.             GetPictureBounds(item, bounds, &matrix);
  161.         } else {
  162.             gxRectangle itemBounds;
  163.             gxMapping tempMatrix;
  164.             gxShape boundsShape = GXNewRectangle( GXGetShapeLocalBounds(item, &itemBounds) );
  165.  
  166.             /* map our bounds gxRectangle through the concatenated gxMapping. note that since GXGetShapeLocalBounds already
  167.             maps the bounds by the item’s gxTransform, we need to undo this effect. */
  168.         #ifdef debugging
  169.             GXIgnoreGraphicsNotice(mapping_unaffected);
  170.         #endif
  171.                 MapMapping(&matrix, InvertMapping(&tempMatrix, GXGetTransformMapping(GXGetShapeTransform(item), &tempMatrix)));
  172.                 GXMapShape(boundsShape, &matrix);
  173.         #ifdef debugging
  174.             GXPopGraphicsNotice();
  175.         #endif
  176.             /* get the bounds of our transformed bounds gxRectangle and then dispose of the temporary bounds gxShape */
  177.             GXGetShapeBounds(boundsShape, 0L, &itemBounds);
  178.             GXDisposeShape(boundsShape);
  179.  
  180.             /* union this item’s bounds into the main picture bounds (work around a problem with GXUnionRectangle) */
  181.             if( bounds->right < bounds->left || bounds->bottom < bounds->top )
  182.                 *bounds = itemBounds;
  183.             else
  184.                 GXUnionRectangle(bounds, bounds, &itemBounds);
  185.         }
  186.     } while( --count );
  187. }
  188.  
  189. static gxRectangle *GetShapeTransformedBounds(gxShape source, gxRectangle *bounds)
  190. {
  191.     gxMapping matrix;
  192.  
  193.     /* since graphics currently can’t get the correct bounds for a picture gxShape, we need special code */
  194.     if( GXGetShapeType(source) == gxPictureType ) {
  195.         bounds->left = bounds->top = gxPositiveInfinity;
  196.         bounds->right = bounds->bottom = gxNegativeInfinity;
  197.         GetPictureBounds( source, bounds, GXGetShapeMapping(source, &matrix) );
  198.         return bounds;
  199.     }
  200.  
  201.     /* otherwise, just use GXGetShapeLocalBounds */
  202.     return GXGetShapeLocalBounds(source, bounds);
  203. }
  204.  
  205. static gxPoint *GetShapeTransformedCenter(gxShape source, gxPoint *center)
  206. {
  207.     gxMapping matrix;
  208.  
  209.     /* since graphics currently can’t calculate the center of picture shapes, we need special code */
  210.     if( GXGetShapeType(source) == gxPictureType ) {
  211.         gxRectangle bounds;
  212.  
  213.         GetShapeTransformedBounds(source, &bounds);
  214.         center->x = (bounds.right >> 1) + (bounds.left >> 1);
  215.         center->y = (bounds.top >> 1) + (bounds.bottom >> 1);
  216.     } else
  217.         MapPoints( GXGetShapeMapping(source, &matrix), 1, GXGetShapeCenter(source, 0L, center) );
  218.     return center;
  219. }
  220.  
  221.  
  222. /*----------------------------------------------------------------------------------------------------*/
  223. /* private internal routines */
  224. /*----------------------------------------------------------------------------------------------------*/
  225.  
  226.  
  227. #if 0
  228. static boolean AllocateOffscreen(shapeControl source)
  229. {
  230.     return false;
  231.     gxBitmap deepestBits;
  232.     gxShape area;
  233.  
  234.     /* this ensures that the first device that we find will be put in deepestBits */
  235.     deepestBits.pixelSize = -1;
  236.  
  237.     area = GXNewShape(gxFullType);
  238.     GXSetShapeViewPorts(area, portCount, ports);
  239.     while( portCount-- ) {
  240.         gxViewDevice devices[maximumViewThings];
  241.         long deviceCount;
  242.  
  243.         if( (deviceCount = GXGetShapeGlobalViewDevices(area, ports[portCount], devices)) > maximumViewThings )
  244.             DebugStr("\pexceded the maximum number of viewDevices for a gxShape control");
  245.         while( deviceCount-- ) {
  246.             gxShape bitsShape;
  247.             gxBitmap bits;
  248.  
  249.             bitsShape = GXGetViewDeviceBitmap(devices[deviceCount]);
  250.             GXGetBitmap(bitsShape, &bits, nil);
  251.             if( bits.pixelSize > deepestBits.pixelSize )
  252.                 deepestBits = bits;
  253.             GXDisposeShape(bitsShape);
  254.         }
  255.     }
  256.     GXDisposeShape(area);
  257.  
  258.     if( deepestBits.pixelSize > 0 ) {
  259.         gxShape bitsShape;
  260.  
  261.         deepestBits.image = nil;    /* make graphics allocate our offscreen bit image */
  262.         bitsShape = GXNewBitmap(&deepestBits, nil);
  263.         CreateOffscreen(&source->buffer, bitsShape);
  264.         GXDisposeShape(bitsShape);
  265.     } else
  266.         FillBytes(&source->buffer, sizeof(source->buffer), 0);
  267.  
  268.     /*** needs work */
  269. }
  270. #endif
  271.  
  272. static void InvalidateRectangle(shapeControlPtr targetPtr, gxRectangle *newArea)
  273. {
  274.     if( targetPtr->invalidAreas ) {
  275.         gxRectangle *listPtr = *targetPtr->invalidAreas;
  276.         gxRectangle *maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  277.  
  278.         while( listPtr != maxPtr &&
  279.             (listPtr->top < newArea->top || listPtr->top == newArea->top && listPtr->left < newArea->left) ) {
  280.             ++listPtr;
  281.         }
  282.         Munger((Handle)targetPtr->invalidAreas, (char *)listPtr - (char *)*targetPtr->invalidAreas, nil, 0,
  283.             (Ptr)newArea, sizeof(gxRectangle));
  284.     } else {
  285.         targetPtr->invalidAreas = (gxRectangle **)NewHandle(sizeof(gxRectangle));
  286.         **targetPtr->invalidAreas = *newArea;
  287.  
  288.         /*** reduce the gxRectangle list here */
  289.     }
  290.  
  291. #if 0  /* re-enable this to show which areas will be updated */
  292.     {    gxShape invalidShape = GXNewRectangle(newArea);
  293.     #ifdef debugging
  294.         GXIgnoreGraphicsNotice(color_already_set);
  295.     #endif
  296.             SetShapeCommonColor(invalidShape, gxBlack);
  297.     #ifdef debugging
  298.         GXPopGraphicsNotice();
  299.     #endif
  300.         GXDrawShape(invalidShape);
  301.         GXDisposeShape(invalidShape);
  302.     }
  303. #endif
  304. }
  305.  
  306. static void InvalidatePicture(shapeControlPtr targetPtr, gxShape control)
  307. {
  308.     /*** later, we need to fix the picture code below and then only invalidate the bounds of the sub-items */
  309.     if( false && GXGetShapeType(control) == gxPictureType ) {
  310.         long count = GXGetPicture(control, nil, nil, nil, nil);
  311.         while( count > 0 ) {
  312.             gxShape item;
  313.             gxTransform itemXform, originalXform;
  314.             gxRectangle bounds;
  315.  
  316.             GXGetPictureParts(control, count, 1, &item, nil, nil, &itemXform);
  317.             if( itemXform ) {
  318.                 originalXform = GXGetShapeTransform(item);
  319.                 GXSetShapeTransform(item, itemXform);
  320.             }
  321.             InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  322.             if( itemXform )
  323.                 GXSetShapeTransform(item, originalXform);
  324.             --count;
  325.         }
  326.     } else {
  327.         gxRectangle bounds;
  328.         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(control, &bounds));
  329.     }
  330. }
  331.  
  332. static void RedrawShapeControl(shapeControlPtr targetPtr)
  333. {
  334.     if( targetPtr->invalidAreas ) {
  335.         gxRectangle *listPtr, *maxPtr;
  336.  
  337.         /* lock down our handle of rectangular invalid areas */
  338.         HLock((Handle)targetPtr->invalidAreas);
  339.         listPtr = *targetPtr->invalidAreas;
  340.         maxPtr = (gxRectangle *)((char *)listPtr + GetHandleSize((Handle)targetPtr->invalidAreas));
  341.  
  342.         do {    gxShape clip;
  343.             gxViewPort *portList, *childList;
  344.             gxViewPort **childPorts;
  345.  
  346.             /* make a gxViewPort list for all of our child ports (these will hold our invalid clip) */
  347.             childPorts = (gxViewPort **)NewHandleClear( GetHandleSize((Handle)targetPtr->originalPorts) );
  348.             childList = DereferenceViewPortList(childPorts);
  349.  
  350.             /* clip all of the viewPorts that we are drawing into to our current invalid gxRectangle */
  351.             clip = GXNewRectangle(listPtr);
  352.             portList = DereferenceViewPortList(targetPtr->originalPorts);
  353.             while( *portList ) {
  354.                 *childList = GXNewViewPort(GXGetViewPortViewGroup(*portList));
  355.                 GXSetViewPortParent(*childList, *portList);
  356.                 GXSetViewPortClip(*childList, clip);
  357.                 ++portList;
  358.                 ++childList;
  359.             }
  360.             ReleaseViewPortList(targetPtr->originalPorts);
  361.             GXDisposeShape(clip);
  362.  
  363.             /* re-direct all of our drawing into the child viewPorts */
  364.         #ifdef debugging    
  365.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  366.         #endif
  367.             {    long portCount = CountViewPortList(childPorts);
  368.  
  369.                 childList = *childPorts;
  370.                 GXSetShapeViewPorts(targetPtr->background, portCount, childList);
  371.                 if( targetPtr->foreground )
  372.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, childList);
  373.                 if( targetPtr->controls )
  374.                     GXSetShapeViewPorts(targetPtr->controls, portCount, childList);
  375.             }
  376.         #ifdef debugging    
  377.             GXPopGraphicsNotice();
  378.         #endif
  379.  
  380.             GXDrawShape(targetPtr->background);
  381.  
  382.             /* draw all the items in the picture, one at a time in the proper order using our clip */
  383.         #if 1
  384.             {    long count = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  385.                 long index;
  386.  
  387.                 for(index = 1; index <= count; ++index ) {
  388.                     gxViewPort **itemList;
  389.                     gxShape item;
  390.  
  391.                     GXGetPictureParts(targetPtr->items, index, 1, &item, nil, nil, nil);
  392.  
  393.                     /* save the item’s gxViewPort list and re-direct the item into our childPorts */
  394.                     itemList = NewViewPortListFromShape(item);
  395.                     GXSetShapeViewPorts(item, CountViewPortList(childPorts), *childPorts);
  396.  
  397.                     GXDrawShape(item);
  398.  
  399.                     /* restore the item’s gxViewPort list and throw away our saved list memory */
  400.                     DereferenceViewPortList(itemList);
  401.                     GXSetShapeViewPorts(item, CountViewPortList(itemList), *itemList);
  402.                     DisposeViewPortList(itemList);
  403.                 }
  404.             }
  405.         #else
  406.             GXDrawShape(targetPtr->items);
  407.         #endif
  408.  
  409.             if( targetPtr->foreground )
  410.                 GXDrawShape(targetPtr->foreground);
  411.             if( targetPtr->controls )
  412.                 GXDrawShape(targetPtr->controls);
  413.  
  414.             /* re-direct all of our drawing back into the main viewPorts */
  415.         #ifdef debugging
  416.             GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  417.         #endif
  418.             {
  419.                 long portCount = CountViewPortList(targetPtr->originalPorts);
  420.  
  421.                 portList = DereferenceViewPortList(targetPtr->originalPorts);
  422.                 GXSetShapeViewPorts(targetPtr->background, portCount, portList);
  423.                 if( targetPtr->foreground )
  424.                     GXSetShapeViewPorts(targetPtr->foreground, portCount, portList);
  425.                 if( targetPtr->controls )
  426.                     GXSetShapeViewPorts(targetPtr->controls, portCount, portList);
  427.                 ReleaseViewPortList(targetPtr->originalPorts);
  428.             }
  429.         #ifdef debugging
  430.             GXPopGraphicsNotice();
  431.         #endif
  432.             /* dispose of all the viewPorts in the child list and then dispose of the child list itself */
  433.             childList = DereferenceViewPortList(childPorts);
  434.             while( *childList )
  435.                 GXDisposeViewPort(*childList++);
  436.             ReleaseViewPortList(childPorts);
  437.             DisposeViewPortList(childPorts);
  438.         } while( ++listPtr < maxPtr );
  439.  
  440.         /* throw away all the invalid areas since we have just updated them */
  441.         DisposeHandle((Handle)targetPtr->invalidAreas);
  442.         targetPtr->invalidAreas = nil;
  443.     }
  444. }
  445.  
  446. static void BuildSelectionControls(shapeControlPtr targetPtr)
  447. {
  448.     gxRectangle bounds, controlBounds;
  449.     gxShape controls, workShape;
  450.  
  451.     /* don’t re-build anything if building is hidden */
  452.     if( targetPtr->buildHiddenLevel )
  453.         return;
  454.  
  455.     /* throw away the old controls and see if there is a selection; if not, then we are done */
  456.     DisposeShapeAt(&targetPtr->controls);
  457.     if( targetPtr->selection == nil )
  458.         return;
  459.  
  460.     /* create a new controls picture in all of our viewPorts */
  461.     controls = targetPtr->controls = GXNewShape(gxPictureType);
  462.     { gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  463.     GXSetShapeViewPorts(targetPtr->controls, CountViewPortList(targetPtr->originalPorts), portList);
  464.     ReleaseViewPortList(targetPtr->originalPorts); }
  465.  
  466.     /* get the bounds of the entire selection and calculate the controlBounds */
  467.     GetShapeTransformedBounds(targetPtr->selection, &bounds);
  468.     IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  469.     controlBounds.left = bounds.left - controlHandleGap;
  470.     controlBounds.top = bounds.top - controlHandleGap;
  471.     controlBounds.right = bounds.right + controlHandleGap;
  472.     controlBounds.bottom = bounds.bottom + controlHandleGap;
  473.  
  474.     /* create the scale controls (these are simply rotated images of each other) */
  475.     {    static long scalePoly[] =    { 1, 4,
  476.                                 ff(0), ff(0),
  477.                                 ff(5) + 0x5000, ff(0),
  478.                                 ff(0), ff(5) + 0x5000,
  479.                                 ff(0), ff(0)
  480.                             };
  481.         gxShape tempShape;
  482.  
  483.         workShape = GXNewPolygons((gxPolygons *)scalePoly);
  484.         GXSetShapeFill(workShape, gxWindingFill);
  485.     #ifdef debugging
  486.         GXIgnoreGraphicsNotice(color_already_set);
  487.     #endif
  488.             SetShapeCommonColor(workShape, gxBlack);
  489.     #ifdef debugging
  490.         GXPopGraphicsNotice();
  491.     #endif
  492.         tempShape = GXCopyToShape(nil, workShape);
  493.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.top);
  494.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  495.         GXDisposeShape(tempShape);
  496.  
  497.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  498.         tempShape = GXCopyToShape(nil, workShape);
  499.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.top);
  500.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  501.         GXDisposeShape(tempShape);
  502.  
  503.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  504.         tempShape = GXCopyToShape(nil, workShape);
  505.         GXMoveShapeTo(tempShape, controlBounds.right, controlBounds.bottom);
  506.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  507.         GXDisposeShape(tempShape);
  508.  
  509.         GXRotateShape(workShape, ff(90), ff(0), ff(0));
  510.         tempShape = GXCopyToShape(nil, workShape);
  511.         GXMoveShapeTo(tempShape, controlBounds.left, controlBounds.bottom);
  512.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  513.         GXDisposeShape(tempShape);
  514.  
  515.         GXDisposeShape(workShape);
  516.     }
  517.  
  518.     /* create the skew controls in (top, right, bottom, left) order */
  519.     {    static long skewPoly[] =    { 1, 5,
  520.                                 -ff(3), ff(0),
  521.                                 ff(0), -ff(3),
  522.                                 ff(3), ff(0),
  523.                                 ff(0), ff(3),
  524.                                 -ff(3), ff(0)
  525.                             };
  526.         gxShape tempShape;
  527.         gxPoint center;
  528.  
  529.         center.x = (bounds.right >> 1) + (bounds.left >> 1);
  530.         center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  531.  
  532.         workShape = GXNewPolygons((gxPolygons *)skewPoly);
  533.         GXSetShapeFill(workShape, gxWindingFill);
  534.     #ifdef debugging
  535.         GXIgnoreGraphicsNotice(color_already_set);
  536.     #endif
  537.             SetShapeCommonColor(workShape, gxBlack);
  538.     #ifdef debugging
  539.         GXPopGraphicsNotice();
  540.     #endif
  541.  
  542.         tempShape = GXCopyToShape(nil, workShape);
  543.         GXMoveShape(tempShape, center.x, controlBounds.top);
  544.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  545.         GXDisposeShape(tempShape);
  546.  
  547.         tempShape = GXCopyToShape(nil, workShape);
  548.         GXMoveShape(tempShape, controlBounds.right + ff(1), center.y);
  549.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  550.         GXDisposeShape(tempShape);
  551.  
  552.         tempShape = GXCopyToShape(nil, workShape);
  553.         GXMoveShape(tempShape, center.x, controlBounds.bottom + ff(3));
  554.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  555.         GXDisposeShape(tempShape);
  556.  
  557.         tempShape = GXCopyToShape(nil, workShape);
  558.         GXMoveShape(tempShape, controlBounds.left, center.y);
  559.         GXSetPictureParts(controls, 0, 0, 1, &tempShape, nil, nil, nil);
  560.         GXDisposeShape(tempShape);
  561.  
  562.         GXDisposeShape(workShape);
  563.     }
  564.  
  565.     /* create the rotate control and its center gxPoint */
  566.     {    gxRectangle tempRect;
  567.         gxPoint center;
  568.  
  569.         /* get the center of the selection (or the individual gxShape) for the rotate control and its center gxPoint */
  570.         if( targetPtr->savedCenter.x != gxPositiveInfinity ) {
  571.             center = targetPtr->savedCenter;
  572.         } else if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 1 ) {
  573.             gxShape item;
  574.  
  575.             GXGetPicture(targetPtr->selection, &item, nil, nil, nil);
  576.             GetShapeTransformedCenter(item, ¢er);
  577.         } else {
  578.             center.x = (bounds.right >> 1) + (bounds.left >> 1);
  579.             center.y = (bounds.top >> 1) + (bounds.bottom >> 1);
  580.         }
  581.  
  582.         /* create the rotate control */
  583.         tempRect.left        = center.x - rotateControlRadius;
  584.         tempRect.top        = center.y - rotateControlRadius;
  585.         tempRect.right        = center.x + rotateControlRadius;
  586.         tempRect.bottom = center.y + rotateControlRadius;
  587.         workShape = NewOval(&tempRect);
  588.         GXSetShapeFill(workShape, gxClosedFrameFill);
  589.     #ifdef debugging
  590.         GXIgnoreGraphicsNotice(color_already_set);
  591.     #endif
  592.             SetShapeCommonColor(workShape, gxBlack);
  593.     #ifdef debugging
  594.         GXPopGraphicsNotice();
  595.     #endif
  596.     #if useFilledOval
  597.         GXSetShapePen(workShape, ff(2));
  598.         GXPrimitiveShape(workShape);
  599.         GXSetShapeFill(workShape, gxWindingFill);
  600.     #endif
  601.         GXSetPictureParts(controls, 1, 0, 1, &workShape, nil, nil, nil);
  602.         GXDisposeShape(workShape);
  603.  
  604.         /* create the rotate center */
  605.         tempRect.left        = center.x - rotateCenterSize;
  606.         tempRect.top        = center.y - rotateCenterSize;
  607.         tempRect.right        = center.x + rotateCenterSize;
  608.         tempRect.bottom = center.y + rotateCenterSize;
  609.         workShape = NewOval(&tempRect);
  610.         GXSetShapeFill(workShape, gxWindingFill);
  611.     #ifdef debugging
  612.         GXIgnoreGraphicsNotice(color_already_set);
  613.     #endif
  614.             SetShapeCommonColor(workShape, gxBlack);
  615.     #ifdef debugging
  616.         GXPopGraphicsNotice();
  617.     #endif
  618.         GXSetPictureParts(controls, 2, 0, 1, &workShape, nil, nil, nil);
  619.         GXDisposeShape(workShape);
  620.     }
  621.  
  622.     /* reset the savedCenter to the “no center saved” value */
  623.     targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  624. }
  625.  
  626. static void TrackShapeControl(shapeControlPtr targetPtr, long command, gxPoint originalClick)
  627. {
  628.     boolean hiddenControls = false;
  629.     boolean rebuildControls = false;
  630.     gxPolar originalPolar;
  631.     gxPoint originalSize;
  632.     gxPoint pivot;
  633.     gxPoint adjust;
  634.     gxMapping oldMatrix;
  635.     gxPoint oldMouse = originalClick;
  636.     Fixed polySrc[10];
  637.     Fixed polyDst[10];
  638.  
  639.     /* if we are rotating, then calculate the pivot gxPoint for the rotation and save the original angle */
  640.     if( command == commandRotate ) {
  641.         gxShape centerShape;
  642.         gxPoint tempPoint;
  643.  
  644.         GXGetPictureParts(targetPtr->controls, commandMoveRotateCenter, 1, ¢erShape, nil, nil, nil);
  645.         GetShapeTransformedCenter(centerShape, &pivot);
  646.         tempPoint.x = originalClick.x - pivot.x;
  647.         tempPoint.y = originalClick.y - pivot.y;
  648.         PointToPolar(&tempPoint, &originalPolar);
  649.         targetPtr->savedCenter = pivot;
  650.     } else if( command >= commandScale && command <= commandSkew + 3 ) {
  651.         gxRectangle bounds;
  652.  
  653.         /* get the bounds of the entire selection for all the commands that require it */
  654.         GetShapeTransformedBounds(targetPtr->selection, &bounds);
  655.         IfDebug(bounds.right < bounds.left || bounds.bottom < bounds.top, "\pthe selection should always have a bounds here");
  656.  
  657.         /* calculate the originalSize of the selection and set the original scale gxMapping to identity */
  658.         originalSize.x = bounds.right - bounds.left;
  659.         originalSize.y = bounds.bottom - bounds.top;
  660.         ResetMapping(&oldMatrix);
  661.  
  662.         /* calculate the pivot (or anchor) gxPoint for the scale */
  663.         switch( command ) {
  664.             case commandScale + leftTop:
  665.                 pivot.x = bounds.right;
  666.                 pivot.y = bounds.bottom;
  667.                 break;
  668.             case commandScale + rightTop:
  669.                 pivot.x = bounds.left;
  670.                 pivot.y = bounds.bottom;
  671.                 break;
  672.             case commandScale + rightBottom:
  673.                 pivot.x = bounds.left;
  674.                 pivot.y = bounds.top;
  675.                 break;
  676.             case commandScale + leftBottom:
  677.                 pivot.x = bounds.right;
  678.                 pivot.y = bounds.top;
  679.                 break;
  680.             default:
  681.                 polySrc[0] = 4;
  682.                 polySrc[1] = bounds.left;
  683.                 polySrc[2] = bounds.top;
  684.                 polySrc[3] = bounds.right;
  685.                 polySrc[4] = bounds.top;
  686.                 polySrc[5] = bounds.right;
  687.                 polySrc[6] = bounds.bottom;
  688.                 polySrc[7] = bounds.left;
  689.                 polySrc[8] = bounds.bottom;
  690.                 BlockMove(&polySrc[0], &polyDst[0], sizeof(polySrc));
  691.         }
  692.  
  693.         /* this is the amount that we must adjust the mouse position by in order to have the scaling come out correct */
  694.         adjust.x = FixedAbs(originalClick.x - pivot.x) - originalSize.x;
  695.         adjust.y = FixedAbs(originalClick.y - pivot.y) - originalSize.y;
  696.     }
  697.  
  698.     while( StillDown() ) {
  699.         gxPoint newMouse;
  700.  
  701.         GXGetViewPortMouse(0, &newMouse);
  702.         if( newMouse.x != oldMouse.x || newMouse.y != oldMouse.y ) {
  703.             Fixed deltaX = newMouse.x - oldMouse.x;
  704.             Fixed deltaY = newMouse.y - oldMouse.y;
  705.             gxMapping newMatrix;
  706.             gxRectangle bounds;
  707.             long count;
  708.             gxShape item;
  709.  
  710.             /* if the controls are not hidden yet, then do so now, unless we are moving the rotate center */
  711.             if( command != commandMoveRotateCenter && ! hiddenControls ) {
  712.                 /* set our flag so that we won’t try to hide the controls again */
  713.                 hiddenControls = true;
  714.  
  715.                 /* mark the area covered by the controls as invalid and then temporarily hide the controls picture */
  716.                 InvalidatePicture(targetPtr, targetPtr->controls);
  717.                 GXSetShapeFill(targetPtr->controls, gxNoFill);
  718.                 RedrawShapeControl(targetPtr);
  719.             }
  720.  
  721.             switch( command ) {
  722.  
  723.                 case commandSelect:
  724.                     /*** needs work */
  725.                     break;
  726.  
  727.                 case commandMove:
  728.                     /* move all the items in the selection, marking their old and new bounds as redraw areas */
  729.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  730.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  731.                     do {
  732.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  733.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  734.                         GXMoveShape(item, deltaX,  deltaY);
  735.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  736.                     } while( --count );
  737.  
  738.                     /* move the controls for all the selected items and update the screen to reflect the new position */
  739.                     GXMoveShape(targetPtr->controls, deltaX, deltaY);
  740.                     RedrawShapeControl(targetPtr);
  741.                     break;
  742.  
  743.                 case commandRotate:
  744.                 {    gxPoint tempPoint;
  745.                     gxPolar newPolar;
  746.  
  747.                     tempPoint.x = newMouse.x - pivot.x;
  748.                     tempPoint.y = newMouse.y - pivot.y;
  749.                     PointToPolar(&tempPoint, &newPolar);
  750.  
  751.                     /* rotate all the items in the selection, marking their old and new bounds as redraw areas */
  752.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  753.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  754.                     do {
  755.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  756.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  757.                         GXRotateShape(item, newPolar.angle - originalPolar.angle, pivot.x, pivot.y);
  758.                         InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  759.                     } while( --count );
  760.  
  761.                     /* copy the newPolar into the originalPolar for our next pass through the loop */
  762.                     originalPolar = newPolar;
  763.  
  764.                     /* invalidate the controls for all the selected items and update the screen to reflect the new position */
  765.                     rebuildControls = true;
  766.                     RedrawShapeControl(targetPtr);
  767.                     break;
  768.                 }
  769.  
  770.                 case commandMoveRotateCenter:
  771.                 {    long index;
  772.  
  773.                     /* move the rotate center control part and the rotate control part */
  774.                     for(index = commandRotate; index <= commandMoveRotateCenter; ++index) {
  775.                         gxShape control;
  776.                         gxTransform xform;
  777.                         long pass;
  778.  
  779.                         GXGetPictureParts(targetPtr->controls, index, 1, &control, nil, nil, &xform);
  780.                         /* invalidate the position before the move and the position after the move */
  781.                         for( pass = 0; pass <= 1; ++pass ) {
  782.                             gxShape boundsShape;
  783.                             gxTransform originalXform;
  784.                             gxMapping matrix;
  785.  
  786.                             if( xform ) {
  787.                                 originalXform = GXGetShapeTransform(control);
  788.                                 GXSetShapeTransform(control, xform);
  789.                             }
  790.                             boundsShape = GXNewRectangle(GetShapeTransformedBounds(control, &bounds));
  791.                             if( xform )
  792.                                 GXSetShapeTransform(control, originalXform);
  793.                             GXMapShape(boundsShape, GXGetShapeMapping(targetPtr->controls, &matrix));
  794.                             GXGetShapeBounds(boundsShape, 0L, &bounds);
  795.                             GXDisposeShape(boundsShape);
  796.                             InvalidateRectangle(targetPtr, &bounds);
  797.  
  798.                             /* if we are on our first pass, then move the control */
  799.                             if( pass == 0 )
  800.                                 GXMoveShape(control, deltaX, deltaY);
  801.                         }
  802.                     }
  803.  
  804.                     /* update the screen to reflect the new positions of these controls */
  805.                     RedrawShapeControl(targetPtr);
  806.                     break;
  807.                 }
  808.  
  809.                 /* scale the gxShape around the pivot gxPoint */
  810.                 case commandScale + leftTop:
  811.                 case commandScale + rightTop:
  812.                 case commandScale + rightBottom:
  813.                 case commandScale + leftBottom:
  814.                 {    Fixed scaleX, scaleY;
  815.  
  816.                     /* calculate the scale values for the selection */
  817.                     scaleX = FixedDivide(FixedAbs(newMouse.x - pivot.x) - adjust.x, originalSize.x);
  818.                     scaleY = FixedDivide(FixedAbs(newMouse.y - pivot.y) - adjust.y, originalSize.y);
  819.  
  820.                     /* if we are on the other side of the pivot gxPoint from where we started, then negate the scale */
  821.                     if( ((newMouse.x - pivot.x) ^ (originalClick.x - pivot.x)) < 0 )
  822.                         scaleX = -scaleX;
  823.                     if( ((newMouse.y - pivot.y) ^ (originalClick.y - pivot.y)) < 0 )
  824.                         scaleY = -scaleY;
  825.  
  826.                     /* save our new true scale gxMapping and calculate the delta gxMapping to apply to all the shapes */
  827.                     ResetMapping(&newMatrix);
  828.                     ScaleMapping(&newMatrix, scaleX, scaleY, pivot.x, pivot.y);
  829.                     goto mapSelection;
  830.                 }
  831.  
  832.                 case commandSkew + centerTop:
  833.                     polyDst[1] += deltaX;
  834.                     polyDst[2] += deltaY;
  835.                     polyDst[3] = polyDst[1] + originalSize.x;
  836.                     polyDst[4] = polyDst[2];
  837.                     goto skewSelection;
  838.  
  839.                 case commandSkew + centerRight:
  840.                     polyDst[3] += deltaX;
  841.                     polyDst[4] += deltaY;
  842.                     polyDst[5] = polyDst[3];
  843.                     polyDst[6] = polyDst[4] + originalSize.y;
  844.                     goto skewSelection;
  845.  
  846.                 case commandSkew + centerBottom:
  847.                     polyDst[7] += deltaX;
  848.                     polyDst[8] += deltaY;
  849.                     polyDst[5] = polyDst[7] + originalSize.x;
  850.                     polyDst[6] = polyDst[8];
  851.                     goto skewSelection;
  852.  
  853.                 case commandSkew + centerLeft:
  854.                     polyDst[1] += deltaX;
  855.                     polyDst[2] += deltaY;
  856.                     polyDst[7] = polyDst[1];
  857.                     polyDst[8] = polyDst[2] + originalSize.y;
  858.                     /* fall through into “skewSelection” */
  859.  
  860. skewSelection:            PolyToPolyMap((gxPolygon *)&polySrc, (gxPolygon *)&polyDst, &newMatrix);
  861.                     /* fall through into “mapSelection” */
  862.  
  863. mapSelection:        {    gxMapping deltaMatrix, tempMatrix;
  864.  
  865.                     /* calculate the delta gxMapping */
  866.                     CopyToMapping(&tempMatrix, &newMatrix);
  867.                     InvertMapping(&deltaMatrix, &oldMatrix);
  868.                     MapMapping(&deltaMatrix, &tempMatrix);
  869.  
  870.                     /* map all the shapes in the selection (and invalidate them) */
  871.                     count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  872.                     IfDebug(count == 0, "\pthe selection should always have items in it here");
  873.                     do {    gxShape itemCopy;
  874.     
  875.                         GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  876.                         itemCopy = GXCopyToShape(nil, item);
  877.                         GXMapShape(itemCopy, &deltaMatrix);
  878.                         GetShapeTransformedBounds(itemCopy, &bounds);
  879.                         GXDisposeShape(itemCopy);
  880.                         if( bounds.right >= bounds.left + minimumObjectSizeH &&
  881.                             bounds.bottom >= bounds.top + minimumObjectSizeV ) {
  882.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  883.                                 GXMapShape(item, &deltaMatrix);
  884.                                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  885.                         } else {
  886.                             /* if the object would have disappeared, then keep the objects old full-delta gxMapping */
  887.                             CopyToMapping(&newMatrix, &oldMatrix);
  888.                         }
  889.                     } while( --count );
  890.  
  891.                     /* save the new full-delta gxMapping in our original gxMapping variable */
  892.                     CopyToMapping(&oldMatrix, &newMatrix);
  893.  
  894.                     /* update the screen to reflect changes to the gxShape and invalidate the controls */
  895.                     RedrawShapeControl(targetPtr);
  896.                     rebuildControls = true;
  897.                     break;
  898.                 }
  899.             }
  900.  
  901.             oldMouse = newMouse;
  902.         }
  903.     }
  904.  
  905.     /* if we changed the geometry of the selection, then re-build the controls for it */
  906.     if( rebuildControls )
  907.         BuildSelectionControls(targetPtr);
  908.  
  909.     if( hiddenControls ) {
  910.         GXSetShapeFill(targetPtr->controls, gxEvenOddFill);
  911.         GXDrawShape(targetPtr->controls);
  912.     }
  913. }
  914.  
  915. static void DeselectShape(shapeControlPtr targetPtr, gxShape item)
  916. {
  917.     if( targetPtr->selection ) {
  918.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  919.         gxShape temp;
  920.  
  921.         do {
  922.             GXGetPictureParts(targetPtr->selection, count, 1, &temp, nil, nil, nil);
  923.         } while( temp != item && --count );
  924.  
  925.         /* if we actually found the item in the selection, then de-select it */
  926.         if( count ) {
  927.             /* invalidate the controls for the selection, since we are changing it */
  928.             InvalidatePicture(targetPtr, targetPtr->controls);
  929.  
  930.             /* remove the item from the selection picture and dispose of the picture item was the only selected gxShape */
  931.             GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  932.             if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 )
  933.                 DisposeShapeAt(&targetPtr->selection);
  934.  
  935.             /* rebuild the controls for the selection */
  936.             BuildSelectionControls(targetPtr);
  937.         }
  938.     }
  939. }
  940.  
  941. static void SelectShape(shapeControlPtr targetPtr, gxShape item, boolean replaceSelection, boolean bringToFront)
  942. {
  943.     /* move the passed item gxShape to the front of the picture if we are supposed to do so */
  944.     if( bringToFront ) {
  945.         long index = GXGetPicture(targetPtr->items, nil, nil, nil, nil);
  946.         gxShape tempShape;
  947.  
  948.         IfDebug(index == 0, "\ptargetPtr->items must always have items");
  949.         do {
  950.             GXGetPictureParts(targetPtr->items, index, 1, &tempShape, nil, nil, nil);
  951.             if( tempShape == item ) {
  952.                 gxRectangle bounds;
  953.  
  954.                 /* add the gxShape to the end first, then remove it so that our references are always valid */
  955.                 GXSetPictureParts(targetPtr->items, 0, 0, 1, &item, nil, nil, nil);
  956.                 GXSetPictureParts(targetPtr->items, index, 1, 0, nil, nil, nil, nil);
  957.                 InvalidateRectangle(targetPtr, GetShapeTransformedBounds(item, &bounds));
  958.             }
  959.         } while( --index );
  960.     }
  961.  
  962.     /* if we are replacing the selection, then un-select all the selected shapes */
  963.     if( replaceSelection && targetPtr->selection ) {
  964.         InvalidatePicture(targetPtr, targetPtr->controls);
  965.         DisposeShapeAt(&targetPtr->selection);
  966.         DisposeShapeAt(&targetPtr->controls);
  967.     }
  968.  
  969.     /* if there is an old selection that we are adding to, then invalidate the area for its controls */
  970.     if( targetPtr->controls )
  971.         InvalidatePicture(targetPtr, targetPtr->controls);
  972.  
  973.     if( item == nil ) {
  974.         DisposeShapeAt(&targetPtr->controls);
  975.         return;
  976.     }
  977.  
  978.     if( targetPtr->selection == nil ) {
  979.         targetPtr->selection = GXNewPicture(1, &item, nil, nil, nil);
  980.     } else {
  981.         long count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  982.         long index = 0;
  983.         gxShape selectionItem;
  984.         gxShape mainItem;
  985.  
  986.         /* look through the selection for the first gxShape AFTER item in the main items picture; insert item before this index */
  987.         IfDebug(count == 0, "\pselection must have items in it here");
  988.         do {    long mainIndex = 0;
  989.  
  990.             GXGetPictureParts(targetPtr->selection, ++index, 1, &selectionItem, nil, nil, nil);
  991.             do {
  992.                 IfDebug(mainIndex == GXGetPicture(targetPtr->items, nil, nil, nil, nil),
  993.                     "\pselected item not in main items picture");
  994.                 GXGetPictureParts(targetPtr->items, ++mainIndex, 1, &mainItem, nil, nil, nil);
  995.             } while( mainItem != selectionItem && mainItem != item );
  996.         } while( mainItem == selectionItem && --count );
  997.  
  998.         /* if we hit the end of the selection without finding a gxShape after item, then set index to zero to insert at the end */
  999.         if( count == 0 )    index = 0;
  1000.  
  1001.         /* insert our newly selected item in the correct place in the selection */
  1002.         GXSetPictureParts(targetPtr->selection, index, 0, 1, &item, nil, nil, nil);
  1003.     }
  1004.  
  1005.     /* calculate the geometry of the controls for the selection and invalidate this new area */
  1006.     BuildSelectionControls(targetPtr);
  1007.     InvalidatePicture(targetPtr, targetPtr->controls);
  1008. }
  1009.  
  1010.  
  1011. /*----------------------------------------------------------------------------------------------------*/
  1012. /* public routines */
  1013. /*----------------------------------------------------------------------------------------------------*/
  1014.  
  1015.  
  1016. shapeControl NewShapeControl(gxShape items, gxShape background, gxShape foreground)
  1017. {
  1018.     shapeControl target;
  1019.  
  1020.     NilShapeReturnNil(items);
  1021.     IfWarningReturnNil(GXGetShapeType(items) != gxPictureType, picture_expected);
  1022.     IfDebug(firstIndexCommand <= lastSpecialCommand, "\pinvalid gxShape control commands - bug in gxShape control library");
  1023.  
  1024.     if( (target = (shapeControl)NewHandleClear(sizeof(shapeControlRecord))) != 0 ) {
  1025.         gxViewPort **originalPorts = NewViewPortListFromShape(items);
  1026.         shapeControlPtr targetPtr;
  1027.  
  1028.         HLock((Handle)target);
  1029.         targetPtr = *target;
  1030.  
  1031.         targetPtr->items = GXCloneShape(items);
  1032.  
  1033.         if( background )
  1034.             targetPtr->background = GXCloneShape(background);
  1035.         else {
  1036.             gxColor whiteColor;
  1037.  
  1038.             /* if there is no background, then create a white full gxShape (we always need some sort of background) */
  1039.             targetPtr->background = GXNewShape(gxFullType);
  1040.             whiteColor.space = gxRGBSpace;
  1041.             whiteColor.profile = nil;
  1042.             whiteColor.element.rgb.red = 0xFFFF;
  1043.             whiteColor.element.rgb.green = 0xFFFF;
  1044.             whiteColor.element.rgb.blue = 0xFFFF;
  1045.             GXSetShapeColor(targetPtr->background, &whiteColor);
  1046.         }
  1047.         /* ensure that the background draws to the same ports as the items */
  1048.         DereferenceViewPortList(originalPorts);
  1049.     #ifdef debugging
  1050.         GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1051.     #endif
  1052.             GXSetShapeViewPorts(targetPtr->background, CountViewPortList(originalPorts), *originalPorts);
  1053.     #ifdef debugging
  1054.         GXPopGraphicsNotice();
  1055.     #endif
  1056.         if( foreground ) {
  1057.             targetPtr->foreground = GXCloneShape(foreground);
  1058.             GXSetShapeViewPorts(targetPtr->foreground, CountViewPortList(originalPorts), *originalPorts);
  1059.         }
  1060.  
  1061.         /* set the savedCenter to the “no center saved” value */
  1062.         targetPtr->savedCenter.x = targetPtr->savedCenter.y = gxPositiveInfinity;
  1063.  
  1064.         ReleaseViewPortList(originalPorts);
  1065.         targetPtr->originalPorts = originalPorts;
  1066.         HUnlock((Handle)target);
  1067.     }
  1068.  
  1069.     return target;
  1070. }
  1071.  
  1072.  
  1073. void DisposeShapeControl(shapeControl target)
  1074. {
  1075.     shapeControlPtr targetPtr;
  1076.  
  1077.     NilParamReturn(target);
  1078.  
  1079.     HLock((Handle)target);
  1080.     targetPtr = *target;
  1081.     GXDisposeShape(targetPtr->items);
  1082.     GXDisposeShape(targetPtr->background);
  1083.     DisposeShapeAt(&targetPtr->foreground);
  1084.     DisposeShapeAt(&targetPtr->selection);
  1085.     DisposeShapeAt(&targetPtr->controls);
  1086.     DisposeViewPortList(targetPtr->originalPorts);
  1087.     HUnlock((Handle)target);
  1088.  
  1089.     /* dispose of the gxShape control memory itself */
  1090.     DisposeHandle((Handle)target);
  1091. }
  1092.  
  1093.  
  1094. gxShape GetShapeControlSelection(const shapeControl source)
  1095. {
  1096.     NilParamReturn(source);
  1097.  
  1098.     if( (*source)->selection )
  1099.         return GXCopyToShape(nil, (*source)->selection);
  1100.     else
  1101.         return nil;
  1102. }
  1103.  
  1104.  
  1105. void SetShapeControlsSelection(shapeControl target, gxShape shapesToSelect, boolean bringToFront, boolean replaceSelection)
  1106. {
  1107.     shapeControlPtr targetPtr;
  1108.     long count;
  1109.     boolean updateControls = false;
  1110.  
  1111.     NilParamReturn(target);
  1112.     HLock((Handle)target);
  1113.     targetPtr = *target;
  1114.  
  1115.     /* prevent the selection’s controls from getting re-build by the various intermediate steps */
  1116.     ++targetPtr->buildHiddenLevel;
  1117.  
  1118.     /* if we are replacing the old selection, then de-select all the old shapes that will not be in the new selection */
  1119.     if( replaceSelection && targetPtr->selection ) {
  1120.         count = GXGetPicture(targetPtr->selection, nil, nil, nil, nil);
  1121.  
  1122.         do {    /* scan through all the items in the selection, de-selecting all the ones not in shapesToSelect */
  1123.             gxShape item;
  1124.             boolean itemInNewSelection;
  1125.  
  1126.             GXGetPictureParts(targetPtr->selection, count, 1, &item, nil, nil, nil);
  1127.             itemInNewSelection = false;
  1128.             if( shapesToSelect == nil || (shapesToSelect != item && GXGetShapeType(shapesToSelect) == gxPictureType
  1129.                 && ShapeInPicture(item, shapesToSelect) == false) ) {
  1130.                     updateControls = true;
  1131.                     GXSetPictureParts(targetPtr->selection, count, 1, 0, nil, nil, nil, nil);
  1132.             }
  1133.         } while( --count );
  1134.  
  1135.         if( GXGetPicture(targetPtr->selection, nil, nil, nil, nil) == 0 ) {
  1136.             DisposeShapeAt(&targetPtr->selection);
  1137.             DisposeShapeAt(&targetPtr->controls);
  1138.         }
  1139.     }
  1140.  
  1141.     /* if there is a new selection, then remove any already selected shapes from it */
  1142.     if( shapesToSelect ) {
  1143.         /* is the new selection a non-empty list of items? */
  1144.         if( GXGetShapeType(shapesToSelect) == gxPictureType && (count = GXGetPicture(shapesToSelect, nil, nil, nil, nil)) != 0) {
  1145.             /*** bring all the new items to the front in the order that they are in shapesToSelect */
  1146.  
  1147.             /* make a copy of the new item list to be selected so that we can munge the list */
  1148.             shapesToSelect = GXCopyToShape(nil, shapesToSelect);
  1149.  
  1150.             do {    /* remove all the already selected items from our new list to select */
  1151.                 gxShape item;
  1152.  
  1153.                 /* if the item in our new selection is already selected, then remove it from our new selection */
  1154.                 GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1155.                 if( targetPtr->selection && ShapeInPicture(item, targetPtr->selection) ||
  1156.                     ShapeInPicture(item, targetPtr->items) == false )
  1157.                         GXSetPictureParts(shapesToSelect, count, 1, 0, nil, nil, nil, nil);
  1158.             } while( --count );
  1159.  
  1160.             /* if there are any items left in the new list to be selected, then add them to the current selection */
  1161.             count = GXGetPicture(shapesToSelect, nil, nil, nil, nil);
  1162.             if (count) {
  1163.                 do {    /* note that we don’t bring these items to the front, since we have already done this */
  1164.                     gxShape item;
  1165.  
  1166.                     GXGetPictureParts(shapesToSelect, count, 1, &item, nil, nil, nil);
  1167.                     SelectShape(targetPtr, item, false, false);
  1168.                     updateControls = true;
  1169.                 } while( --count );
  1170.             }
  1171.  
  1172.             /* dispose of our munged copy of the new items that have been selected */
  1173.             GXDisposeShape(shapesToSelect);
  1174.         } else if( ShapeInPicture(shapesToSelect, targetPtr->selection) == false ) {
  1175.             if( ShapeInPicture(shapesToSelect, targetPtr->items) ) {
  1176.                 SelectShape(targetPtr, shapesToSelect, bringToFront, false);
  1177.                 updateControls = true;
  1178.             }
  1179.         }
  1180.     }
  1181.  
  1182.     /* allow the selection’s controls to get re-built and re-build them if they have changed */
  1183.     --targetPtr->buildHiddenLevel;
  1184.     if( updateControls )
  1185.         BuildSelectionControls(targetPtr);
  1186.  
  1187.     HUnlock((Handle)target);
  1188. }
  1189.  
  1190.  
  1191. gxShape GetShapeControlSelectionHandles(const shapeControl source)
  1192. {
  1193.     NilParamReturn(source);
  1194.  
  1195.     if( (*source)->controls )
  1196.         return GXCopyDeepToShape(nil, (*source)->controls);
  1197.     else
  1198.         return nil;
  1199. }
  1200.  
  1201.  
  1202. boolean SendEventToShapeControl(shapeControl target, EventRecord *event)
  1203. {
  1204.     boolean handledEvent = false;
  1205.  
  1206.     NilParamReturn(target);
  1207.     NilParamReturn(event);
  1208.  
  1209.     HLock((Handle)target);
  1210.     switch( event->what ) {
  1211.         case keyDown:
  1212.             break;    /*** change this to handle the key and return true if it is ours */
  1213.         case mouseDown:
  1214.         {    gxPoint originalClick;
  1215.             gxHitTestInfo result;
  1216.             gxShape itemShape;
  1217.             long command;
  1218.             shapeControlPtr targetPtr = *target;
  1219.  
  1220.             {    gxViewPort originalPort = *DereferenceViewPortList(targetPtr->originalPorts);
  1221.                 GXConvertQDPoint(&event->where, originalPort, &originalClick);
  1222.             }
  1223.  
  1224.             /* if there is a selection, then see if any of the selection’s controls have been hit and track them if they have */
  1225.             if( targetPtr->selection ) {
  1226.                 if( GXHitTestPicture(targetPtr->controls, &originalClick, &result, 1, 1) != gxNoPart ) {
  1227.                     TrackShapeControl(targetPtr, result.containerIndex, originalClick);
  1228.                     handledEvent = true;
  1229.                     break;
  1230.                 }
  1231.             }
  1232.  
  1233.             /* see if an item gxShape was hit; if so then add it to the selection and track it */
  1234.             if( GXHitTestPicture(targetPtr->items, &originalClick, &result, 1, 1) != gxNoPart ) {
  1235.                 command = commandMove;
  1236.                 itemShape = result.which;
  1237.  
  1238.                 if( targetPtr->selection && ShapeInPicture(itemShape, targetPtr->selection) ) {
  1239.                     if( event->modifiers & shiftKey ) {
  1240.                         /* the gxShape was already selected and we shift-clicked it, so deselect it and return */
  1241.                         DeselectShape(targetPtr, itemShape);
  1242.                         RedrawShapeControl(targetPtr);
  1243.                         handledEvent = true;
  1244.                         break;
  1245.                     }
  1246.                 } else {
  1247.                     /* select the new gxShape - the last two parameters are (boolean replaceSelection, bringToFront) */
  1248.                     SelectShape(targetPtr, itemShape, (event->modifiers & shiftKey) == 0,
  1249.                         (event->modifiers & optionKey) != 0);
  1250.                     RedrawShapeControl(targetPtr);
  1251.                 }
  1252.  
  1253.                 /* allow the user to move the entire selection around */
  1254.                 TrackShapeControl(targetPtr, commandMove, originalClick);
  1255.                 handledEvent = true;
  1256.                 break;
  1257.             }
  1258.  
  1259.             /* see if we are within the bounds of any of the original viewPorts of the items */
  1260.             {    gxShape testShape = GXNewPoint(&originalClick);
  1261.                 gxViewPort *portList = DereferenceViewPortList(targetPtr->originalPorts);
  1262.  
  1263.             #ifdef debugging
  1264.                 GXIgnoreGraphicsNotice(transform_viewPorts_already_set);
  1265.             #endif
  1266.                     GXSetShapeViewPorts(testShape, CountViewPortList(targetPtr->originalPorts), portList);
  1267.             #ifdef debugging
  1268.                 GXPopGraphicsNotice();
  1269.             #endif
  1270.                 /* scan through all the original viewPorts of the items */
  1271.                 while( *portList ) {
  1272.                     /* if a gxPoint at the original click location would draw to any devices in this port, then we are
  1273.                     within the bounds of the port. we should now drag out a gray gxRectangle to select all the items
  1274.                     contained within it */
  1275.                     if( GXGetShapeGlobalViewDevices(testShape, *portList, nil) ) {
  1276.                         GXDisposeShape(testShape);
  1277.  
  1278.                     #if 0     /*** needs work */
  1279.                         TrackShapeControl(targetPtr, commandSelect, originalClick);
  1280.                     #else
  1281.                         /* for now, just de-select all the items */
  1282.                         SelectShape(targetPtr, nil, true, false);
  1283.                     #endif
  1284.  
  1285.                         RedrawShapeControl(targetPtr);
  1286.                         ReleaseViewPortList(targetPtr->originalPorts);
  1287.                         HUnlock((Handle)target);
  1288.                         return true;
  1289.                     }
  1290.                     ++portList;
  1291.                 }
  1292.                 GXDisposeShape(testShape);
  1293.                 ReleaseViewPortList(targetPtr->originalPorts);
  1294.             }
  1295.  
  1296.             /* nothing was hit, so break and return that we didn’t handle the event */
  1297.             break;
  1298.         }
  1299.     }
  1300.  
  1301.     HUnlock((Handle)target);
  1302.     return handledEvent;
  1303. }
  1304.  
  1305.  
  1306. void InvalidateShapeControlShape(shapeControl target, gxShape invalidShape)
  1307. {
  1308.     NilParamReturn(target);
  1309.     NilShapeReturn(invalidShape);
  1310.  
  1311.     HLock((Handle)target);
  1312.     InvalidatePicture(*target, invalidShape);
  1313.     HUnlock((Handle)target);
  1314. }
  1315.  
  1316.  
  1317. void InvalidateShapeControlRectangle(shapeControl target, gxRectangle *bounds)
  1318. {
  1319.     NilParamReturn(target);
  1320.     NilParamReturn(bounds);
  1321.  
  1322.     HLock((Handle)target);
  1323.     InvalidateRectangle(*target, bounds);
  1324.     HUnlock((Handle)target);
  1325. }
  1326.  
  1327.  
  1328. void UpdateShapeControl(shapeControl target)
  1329. {
  1330.     NilParamReturn(target);
  1331.     HLock((Handle)target);
  1332.     RedrawShapeControl(*target);
  1333.     HUnlock((Handle)target);
  1334. }
  1335.